Bucles

Un bucle (o loop) es una estructura de código que permite repetir una instrucción o un conjunto de instrucciones muchas veces y de forma muy rápida (tanto como sea capaz el ordenador que ejecuta el código). Realmente los bucles son la base de una programación eficiente, pues aprovechan un de las mayores ventajas de los ordenadores (la velocidad de proceso) para realizar una tarea.

Básicamente tenemos dos tipos de bucles: los que se repiten un número determinado de veces, y luego terminan. Y los que se repiten de manera indefinida mientras sea cierta una condición, y sólo terminarán cuando la condición que los mantiene deje de ser cierta.

Los bucles son herramientas poderosas y muy útiles pero también tienen el problema de que pueden hacer caer al ordenador en un proceso sin fin que puede provocar que el documento ( o a veces el propio navegador) deje de funcionar. Esto se debe, normalmente, a que hemos programado mal la condición de salida del bucle, haciendo que nunca se llegue a ella, y por lo tanto el proceso sea infinito.

Bucles con un número determinado de repeticiones

El principal bucle de este tipo es el que definimos con la expresión for(...). En esta expresión ponemos entre paréntesis 3 argumentos que definen el comportamiento del bucle. Básicamente se trata de llevar un contador que marque cuantas veces se tiene que repetir algo. En los paréntesis pondremos en primer lugar cual es el inicio del contador, a continuación se define la condición que hace que se incremente el contador (o número total de veces a repetir), y por último definimos el valor de incremento del contador, que suele ser uno. Su forma es esta: for(valor_inicial;condición_cambio;valor_incremento) {instrucciones a repetir}

Por ejemplo, si queremos mostrar un mensaje al usuario en un alert() tres veces seguidas haremos algo así:


            for(let i=1; i <= 3; i++) {
                alert('Este mensaje se repite 3 veces');
            }
        

Ejemplo

Al pulsar en el siguiente botón vamos a añadir 12 elementos a una lista de tipo ul:

El primer intento de función sería este:


            function creaLista() {
                for(let i = 1; i <=12; i++) {
                    document.getElementById('listado').innerHTML += '<li>Elemento ' + i + '</li>';
                }
            }
        

Este bucle presenta un problema de eficiencia, puesto que forzamos al navegador a acceder 12 veces al DOM para modificar su contenido. Sería preferible preparar el contenido completo a mostrar dentro del bucle y una vez que esté completo pasarlo al DOM en una sola instrucción. Y esto es una norma general cuando trabajemos con bucles, el mimimizar los procesos de acceso y escritura en el DOM. Este planteamiento es más correcto:


            function creaLista() {
                let contenido = '';
                for(let i = 1; i <=12; i++) {
                    contenido += '<li>Elemento ' + i + '</li>';
                }
                document.getElementById('listado').innerHTML = contenido;
            }
        

Además, de esta forma solucionamos el problema de que cada vez que pulsamos al botón se añadan 12 elementos más a los existentes en la lista. Ahora en cada llamada a la función se resetea la variable contenido, lo que hace que los 12 nuevos elementos que acaba conteniendo reemplacen a los existentes en la lista.

Uso de bucle for para recorrer los elementos de un array

Un array es un conjunto de datos agrupados en una variable y organizados por un índice, donde el primer elemento siempre tiene como índice el valor cero.

Los bucles de tipo for son una herramienta idónea para acceder a cada uno de los elementos de un array y trabajar con él. De hecho es una combinación que vamos a ver y utilizar mucho: un array de datos junto con un bucle para recorrerlo.

El esquema para recorrer un array mediante un bucle for será:


            for(let i = 0; i < array.length; i++) {
                array[i]....
            }
        

Donde i=0 hace referencia al primer elemento del array, y la expresión array.length devuelve como valor numérico la cantidad de elementos contenidos en el array. Además, la expresión array[i] accede al elemento concreto del array que tiene el índice i, que como es un valor que va cambiando, recorrerá todos los índices del array, tal y como lo define el bucle for.

Ejemplo

Al pulsar el siguiente botón generamos una lista ul con los días de la semana, que previamente se encuentran almacenados en un array:


            function diaSemana() {
                let dias = '';
                for(let i =0; i < dias_semana.length; i++) {
                    dias += '<li>' + dias_semana[i] +'</li>';
                }
                document.getElementById('semana').innerHTML = dias;
            }
        

Cuando se trata de recorrer un array, el bucle for tiene una variación específica que simplifica el acceso a los elementos. Su estructura es for(cada_elemento of array) {elemento...}. Donde cada_elemento es una variable que hace referencia a cada uno de los items que compone el array de forma secuencial. Este comportamiento es definido el uso de la palabra of dentro de la definición del bucle.

ATENCIÓN: Este método solo funciona con arrays de tipos de datos string, número, booleanos, y con colecciones de objetos (por ejemplo, colecciones de elementos HTML).Pero no sirve para recorrer la lista de propiedades de un objeto. En ese caso se usa la variante for...in

De esta forma, la función anterior puede escribirse también así:


            function diaSemana() {
                let dias = '';
                for( let cada_dia of dias_semana) {
                    dias += '<li>' + cada_dia +'</li>';
                }
                document.getElementById('semana').innerHTML = dias;
            }
        

Bucles con número indeterminado de repeticiones

Este tipo de bucles se basan en una condición que deber darse para que se ejecute el bucle, y que mientras esa condición se evalúe como verdadera hará que el bucle siga ejecutándose.

Existen dos variantes de este bucle: La que comprueba inicialmente la condición y solo si es verdadera ejecuta el bucle, y por lo tanto si inicialmente no es verdadera hará que nunca entremos en el bucle. Y la que ejecuta siempre una primera iteración del bucle, y después comprueba la condición para saber si debe seguir iterando o no.

Las expresiones en este tipo de bucles son while(condición_que_posibilita_el_bucle) {...instrucciones a ejecutar...}, en el caso de comprobación previa para entrar en el bucle. Y la expresión do {...instrucciones a ejecutar...} while(condición)

Ejemplo while()

En este ejemplo introducimos una cantidad inicial de la que podremos ir restando gastos mientras que la cantidad sea positiva. Una vez llegue a cero o a un valor negativo ya no se repite la petición de gasto:


            function bucleWhile() {
                let presupuesto = prompt('Dime presupuesto inicial');
                presupuesto = parseFloat(presupuesto);
                while(presupuesto > 0) {
                    let gasto = parseFloat(prompt('Intro gasto'));
                    presupuesto -= gasto;
                    alert('Nos queda ' + presupuesto + 'restante');
                }
            }
        

Ejemplo de bucle do{} while()

En este caso inicialmente siempre aparecerá un prompt pidiendo una cantidad numérica. Y seguirá apareciendo mientras que el dato introducido no sea un valor numérico.


            function bucleDo() {
                let presupuesto;
                do {
                    presupuesto = prompt('Intro cantidad presupuesto');
                    presupuesto = parseFloat(presupuesto);
                } while (isNaN(presupuesto));
            }
        

Hay que resaltar que definimos la variable presupuesto fuera del bucle porque si lo hiciésemos dentro de la instrucción do usando la expresión let presupuesto = prompt(...), esa variable solo existiría dentro del bloque do, pero no la reconocería la expresión isNaN(presupuesto), provocando un error.

Frente a esto tenemos dos opciones, o declarar la variable fuera del bucle, como en el ejemplo, o declararla usando la expresión var, que hace que la variable exista dentro de toda la función, además de en su bloque:


            function bucleDo() {
                do {
                    var presupuesto = prompt('Intro cantidad presupuesto');
                    presupuesto = parseFloat(presupuesto);
                    console.log(presupuesto)
                } while (isNaN(presupuesto));
            }
        

Uso de break para salir de un bucle

Cuando necesitamos terminar la iteración de un bucle, con independencia del valor de la condición que lo posibilita, podemos usar la instrucción break, que permite acabar inmediatamente saliendo de la iteración actual y continúa leyendo el resto de script.

Hay que tener en cuenta que si trabajamos con bucles anidados y ponemos una instrucción break en el bucle interno, saldremos únicamente de ese bucle, pero continuaremos en el bucle externo. Si queremos terminar por completo de todos los bucles hay que controlar esa situación de forma específica. Para esto puede venir bien el uso de una variable booleana.

En el siguiente ejemplo, el cuadro sigue apareciendo mientras no introduzcamos un número, pero podemos detenerlo tecleando una letra "S":


            function bucleBreak() {
                let presupuesto;
                do {
                    presupuesto = prompt('Intro cantidad presupuesto.\nTeclea "S" para salir');
                    if(presupuesto =='S' || presupuesto == 's') {
                        alert('Has cancelado el proceso. Adiós!')
                        break;
                    }
                    presupuesto = parseFloat(presupuesto);
                } while (isNaN(presupuesto));
            }
        

Si queremos comprobar tanto que la cantidad introducida es numérica, como que es un valor positivo, pondremos algo así en la condición while: while (isNaN(presupuesto) || presupuesto <= 0)

Uso de la expresión continue para saltar una iteración (y seguir en la siguiente)

En este caso la instrucción continue permite saltar un ciclo del bucle y pasar directamente al siguiente sin detenerlo, es útil cuando queremos evitar determinados supuestos dentro de la iteración pero no salir del bucle por ello.

En el siguiente ejemplo, cuando introduzcamos una cantidad de gasto superior a la cantidad de presupuesto restante, mostramos un mensaje de aviso y no realizamos el cálculo que debe hacer cada iteración del bucle (la resta del gasto), para volver a pedir de nuevo una cantidad de gasto válida.


            function bucleContinue() {
                let presupuesto = prompt('Dime presupuesto inicial');
                presupuesto = parseFloat(presupuesto);
                while(presupuesto > 0) {
                    let gasto = parseFloat(prompt('Intro gasto'));
                    if( gasto > presupuesto) {
                        alert('DENEGADO!!!\nTu presupuesto máximo es ' + presupuesto);
                        continue;
                    }
                    presupuesto -= gasto;
                    alert('Nos queda ' + presupuesto + ' restante');
                }
            }
        

Ejemplo de diferentes bucles anidados

En este ejemplo vamos a combinar los bucles vistos anteriormente para realizar el proceso de presupuesto/gasto de forma completa:


            function todoJunto() {
                let cancelar = false;
                let presupuesto;
                do {
                    presupuesto = prompt('Teclea presupuesto inicial. Tiene que ser una cantidad positiva.\nTeclea "salir" para terminar el proceso');
                    if( presupuesto == 'salir' || presupuesto == 'SALIR') {
                        alert('Has cancelado el proceso');
                       cancelar = true;
                    }
                    presupuesto = parseFloat(presupuesto);
                    if(cancelar) {
                        break;
                    }
                } while(isNaN(presupuesto) || presupuesto <= 0);
                while ( presupuesto > 0) {
                    let gasto;
                    if (cancelar) {
                            break;
                        }
                    do {
                        gasto = prompt('Introduce un gasto positivo, menor o igual que el presupuesto.\nTeclea "salir" para terminar');
                        if(gasto == 'salir' || gasto == 'SALIR') {
                            alert('Proceso terminado por el usuario.');
                           cancelar = true;
                        }
                        gasto = parseFloat(gasto);
                        if (cancelar) {
                            break;
                        }
                        if (gasto > presupuesto) {
                            alert('DENEGADO!!\nNo puedes gastar más de ' + presupuesto);
                            //saltamos iteración
                            continue;
                        }
                        if (gasto < 0) {
                            alert('Gasto negativo. Corrígelo');
                        }
                        if (gasto > 0) {
                           presupuesto -= gasto;
                           alert('Gasto aprobado.\nEl saldo restante es ' + presupuesto);
                        }
                    } while (isNaN(gasto));
                }
            }